import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.lang.Math;
import static java.lang.System.out;  //debugging

/** Class Player */
//public class Player extends MouseAdapter implements MouseListener, MouseMotionListener, Runnable {
public class Player extends MouseAdapter implements Runnable {
        //current court coordinates of player (at top left corner of player img)
        private int currXPos=0, currYPos=0;
        //new court coordinates for player 
        private int newXPos=0, newYPos=0;         
        //mouse pointer distance from top left corner of player image
        private int xMouseDelta=0, yMouseDelta=0; 
        private String name="";                            //player name
	private Position position=Position.FRONT_CENTER;  //player position
        private Role role=Role.DEFENDER;                  //player role
        private Role currRole=Role.DEFENDER;              //player current role
        private Height height=Height.AVERAGE;             //player height
	private java.net.URL imgURL=null;                 //URL of player image
        private boolean playerPressed=false;              //player playerPressed by mouse flag
        private int imgHeight=0, imgWidth=0;              //player image height/width
        private boolean playerMoving=false;               //player moving on court flag
        
        //Default size of player image
        public static final int PLAYER_IMG_WIDTH=50, PLAYER_IMG_HEIGHT=60;
        
        /** Enum Position */
        public enum Position {
            FRONT_LEFT, FRONT_CENTER, FRONT_RIGHT, 
            BACK_LEFT, BACK_CENTER, BACK_RIGHT, NONE
        }
        
        /** Enum Role */
        public enum Role {
            SETTER, POWER_HITTER, WEAKSIDE_HITTER, MID_BLOCKER, DEFENDER, 
            LIBERO 
        }
        
        /** Enum Height */
        public enum Height {
            SHORT, AVERAGE, TALL 
        }
        
        /** Enum MoveDirection */
        private enum MoveDirection {
            UNDEFINED, NORTH, SOUTH, EAST, WEST, NORTHWEST, NORTHEAST,
            SOUTHWEST, SOUTHEAST
        }
        
        /** Default Constructor */
	public Player() {}
        
        /** Constructor */
	public Player(String loc, int x, int y, Position pos) {            
            imgURL = getClass().getResource(loc);
            currXPos = newXPos = x;
            currYPos = newYPos = y;
            position = pos;   
	}
        
        /** Constructor */
        public Player(String loc, int x, int y, Height height, Position pos ) {
            imgURL = getClass().getResource(loc);
            currXPos = newXPos = x;
            currYPos = newYPos = y;
            position = pos;
            this.setHeight(height); //set player image size based on height
	}
        
        /** Constructor */
        public Player(String loc, int x, int y, String nameArg, 
                      Height height,
                      Position pos,
                      Role role) {
            imgURL = getClass().getResource(loc);
            currXPos = newXPos = x;
            currYPos = newYPos = y;
            this.name = nameArg;
            this.position = pos;
            this.role = role;
            this.setHeight(height);     //set player image size based on height
            this.setCurrentRole(role);  
	}
        
        /** setImgUrl */
        public void setImgUrl(String loc) {imgURL = getClass().getResource(loc);}
	
        /** getImgUrl */
	public java.net.URL getImgUrl() {return imgURL;}
        
        /** getPlayerImageHeight */
        public int getPlayerImageHeight() {return imgHeight;}
        
        /** getPlayerImageWidth */
        public int getPlayerImageWidth() {return imgWidth;}
	
        /** setXPos */
	public void setXPos(int x) {
            //if player is being dragged, set both current and new coordinate
            if(this.playerPressed){currXPos = newXPos = x;}
            else {currXPos = x;}
	}
	
        /** getXPos */
	public int getXPos() {return currXPos;}
	
        /** setYPos */
	public void setYPos(int y) {
            //if player is being dragged, set both current and new coordinate
            if(this.playerPressed){currYPos = newYPos = y;}
            else {currYPos = y;}
	}
	
        /** getYPos */
	public int getYPos() {return currYPos;}
        
        /** setName */
        public void setName(String nameArg) {this.name = nameArg;}
	
        /** getName */
	public String getName() {return name;}
        
        /** setRole */
        public void setRole(Role roleArg) {this.role = roleArg;}
	
        /** getRole */
	public Role getRole() {return role;}
        
        /** setCurrentRole */
        public void setCurrentRole(Role role) {this.currRole = role;}
        
        /** getRole */
	public Role getCurrentRole() {return currRole;}
        
        /** inBackRow */
        public boolean inBackRow() {
            if (this.position == Position.BACK_LEFT || 
                this.position == Position.BACK_CENTER ||
                this.position == Position.BACK_RIGHT){
                return true;
            }
            return false;
        }
	
        /** setPosition */
	public void setPosition(Position p) {this.position = p;}
	
        /** getPosition */
	public Position getPosition() {return position;}
        
        /** setHeight */
        public void setHeight(Height h) {
            this.height = h;
            //set image size based on player height
            switch(this.height){
                case TALL:  imgHeight = PLAYER_IMG_HEIGHT+10;
                            imgWidth = PLAYER_IMG_WIDTH+10;
                            break;
                case SHORT: imgHeight = PLAYER_IMG_HEIGHT-10;
                            imgWidth = PLAYER_IMG_WIDTH-10;
                            break;
                case AVERAGE: 
                default:    imgHeight = PLAYER_IMG_HEIGHT;
                            imgWidth = PLAYER_IMG_WIDTH;
                            break;
            }
	}
	
        /** getHeight */
	public Height getHeight() {return height;}
        
        /** setPlayerLocation */
        public void setPlayerLocation(int newX, int newY) {
            this.newXPos = newX;
            this.newYPos = newY;
	}
        
        /** setPlayerLocation */
        public void setPlayerLocation(Court.Location newLocation) {
            this.newXPos = newLocation.x;
            this.newYPos = newLocation.y;
	}
        
        /** getPlayerLocation */
        public Court.Location getPlayerLocation() {
            return new Court.Location(this.currXPos, this.currYPos);
	}
        
        /* Checks to see if player is located at the coordinates provided.
         * 
         * @return - true if player's current coordinates matches the provided
         * coordinates.
         */ 
        public boolean inLocation(int locX, int locY) {
            return ((this.getXPos() == locX) && (this.getYPos() == locY));
        }
        
        /** inLocation */
        public boolean inLocation(Court.Location loc) {
            return ((this.getXPos() == loc.x) && (this.getYPos() == loc.y));
        }

        /* Determines if the provided coordinates fall within the 
         * boundary of the player image.
         * 
         * @return - true if coordinates are within boundary of image
         */ 
        private boolean playerPressed(int eX, int eY) {
            if (eX < this.currXPos || (eX > (this.currXPos + this.imgWidth))){
                return false;
            }
            if (eY < this.currYPos || (eY > (this.currYPos + this.imgHeight))){
                return false;
            }
            return true;
        }
        
        //TODO: Implement function to check to verify that player remains
        //      on screen
        private boolean playerWithinBounds(int eX, int eY) {
            /*if ((eX  > __xMax) || (eX < 0)){
                out.println("Player out of bounds");
                return false;
            }
            if ((eY > __yMax) || (eY < 0)){
                out.println("Player out of bounds");
                return false;
            }*/
            return true;
        }
              
        /* Determines the best move for the player to make from its current
         * coordinates to the ones provided.
         * 
         * Returns the coordinates of the next location that the player should
         * move to reach the new coordinates.
         */
        private Court.Location determineNextBestMove(int eX, int eY) {
            //initialize result to player's current location
            Court.Location result = new Court.Location(this.getXPos(), 
                                                       this.getYPos());
            
            //amount of steps the player will make for each move
            final int MOVE_X = 2;
            final int MOVE_Y = 2;
            
            //stores the direction that the player should move
            MoveDirection direction = MoveDirection.UNDEFINED;

            //get the distance from player's current position to destination 
            int deltaX = Math.abs(this.getXPos() - eX);
            int deltaY = Math.abs(this.getYPos() - eY);
  
            //use the distances to determine direction of next move
            if(deltaX != 0 && deltaY != 0){
                //both are nonzero
                if(deltaX == deltaY){
                    //both are equal, so make a horizontal and a vertical move
                    if((this.getXPos() < eX) && (this.getYPos() > eY)){direction = MoveDirection.NORTHEAST;}
                    else if((this.getXPos() > eX) && (this.getYPos() > eY)){direction = MoveDirection.NORTHWEST;}
                    else if((this.getXPos() > eX) && (this.getYPos() < eY)){direction = MoveDirection.SOUTHWEST;}
                    else if((this.getXPos() < eX) && (this.getYPos() < eY)){direction = MoveDirection.SOUTHEAST;}
                }
                //chose the greatest distance in x or y
                else if (deltaX > deltaY){
                    if(this.getXPos() < eX){direction = MoveDirection.EAST;}
                    else {direction = MoveDirection.WEST;}
                }
                else {
                    if(this.getYPos() < eY){direction = MoveDirection.SOUTH;}
                    else {direction = MoveDirection.NORTH;}
                }
            }
            else{
                //one is 0, so determine if move will be vertical or horizontal
                if ((deltaX != 0)){
                    if(this.getXPos() < eX){direction = MoveDirection.EAST;}
                    else {direction = MoveDirection.WEST;}
                }
                else {
                    if(this.getYPos() < eY){direction = MoveDirection.SOUTH;}
                    else {direction = MoveDirection.NORTH;}
                }
            }
            
            //set result coordinates based on direction
            switch (direction)
            {
                case NORTH: 
                    result.y = this.getYPos() - MOVE_Y; //move up
                    break;
                case SOUTH: 
                    result.y = this.getYPos() + MOVE_Y; //move down
                    break;
                case EAST:  
                    result.x = this.getXPos() + MOVE_X; //move right
                    break;
                case WEST:  
                    result.x = this.getXPos() - MOVE_X; //move left
                    break;
                case NORTHEAST:  
                    result.y = this.getYPos() - MOVE_Y; //move up
                    result.x = this.getXPos() + MOVE_X; //move right
                    break;
                case SOUTHEAST:  
                    result.y = this.getYPos() + MOVE_Y; //move down
                    result.x = this.getXPos() + MOVE_X; //move right
                    break;
                case SOUTHWEST:
                    result.y = this.getYPos() + MOVE_Y; //move down
                    result.x = this.getXPos() - MOVE_X; //move left
                    break;
                case NORTHWEST:  
                    result.y = this.getYPos() - MOVE_Y; //move up
                    result.x = this.getXPos() - MOVE_X; //move left
                    break;
                case UNDEFINED:
                default: 
                    out.println("Invalid move");
                    break;
            }
            
            //set to final coordinate position if close enough but not at position
            if((result.x != this.getXPos()) && (deltaX < MOVE_X)){result.x = eX;}
            if((result.y != this.getYPos()) && (deltaY < MOVE_Y)){result.y = eY;}

            return result; //return coordinates of best location to move
        } // end determineNextBestMove
        
        /** mousePressed */
        public void mousePressed(MouseEvent e) {
            //check to see if this player was pressed by user and 
            //the player is not currently moving
            if (playerPressed(e.getX(),e.getY()) && !this.playerMoving){
                //this player has been pressed
                this.playerPressed = true; 
                /* store delta position of pointer from top left corner of 
                   image (needed to redraw image at new position)*/
                xMouseDelta = e.getX() - getXPos();
                yMouseDelta = e.getY() - getYPos();
            }
        }

        /** mouseReleased */
        public void mouseReleased(MouseEvent e) {
            //verify this player was the one pressed by the mouse and 
            //the player is not currently moving
            if (this.playerPressed && !this.playerMoving){
                /* find the coordinates of the top left corner of player 
                   in new position */
                int newX = e.getX() - xMouseDelta;
                int newY = e.getY() - yMouseDelta;
                //TODO: verify player is still in window before setting its position
                //set new position of player
                this.setXPos(newX);
                this.setYPos(newY);
                playerPressed = false;
            }
        }
        
        /** mouseDragged */
        public void mouseDragged(MouseEvent e) {
            if (this.playerPressed && !playerMoving){
                /* find the coordinates of the top left corner of player 
                   in new position */
                int newX = e.getX() - xMouseDelta;
                int newY = e.getY() - yMouseDelta;
                //verify player image is still on the court
                if(playerWithinBounds(newX, newY)){
                    this.setXPos(newX);
                    this.setYPos(newY);
                }
            }
        }
        
        // ignore for now
        public void mouseMoved(MouseEvent e) {}
        public void mouseEntered(MouseEvent e) {
            /*System.out.format("Mouse Pos: %d  %d%n",
                              e.getX(),e.getY());
            if (this.playerPressed(e.getX(),e.getY())) {
                System.out.format("Player %s entered%n",
                                  this.getName());
            }*/
        }
        public void mouseExited(MouseEvent e) {
            /*if (playerPressed(e.getX(),e.getY())) {
                System.out.format("Player %s exited%n",
                                  this.getName());
            }*/
        }
        public void mouseClicked(MouseEvent e) {}   
        
        /** movePlayer */
        private void movePlayer(){
            Court.Location newLocation;
            
            //check to see if player's new location has changed
            if(!inLocation(newXPos, newYPos))
            {
                //move player to new destination
                newLocation = determineNextBestMove(newXPos, newYPos);
                this.setXPos(newLocation.x);
                this.setYPos(newLocation.y);
                //set flag to prevent user from moving the player until
                //the player stops moving
                playerMoving = true;
            }
            else if(inLocation(newXPos, newYPos) && playerMoving) {
                //set flag so user can manually move the player
                playerMoving = false;
            }
        }
        
        /** run */
        public void run() {
            try {
                threadMessage("Player thread started.");
                while(true){
                    // Pause for x seconds
                    Thread.sleep(40);
                    movePlayer(); 
                }
            } catch (InterruptedException e) {
                threadMessage("Player thread interrupted!");
            }
        }
        
        /** threadMessage */
        static void threadMessage(String message) {
            String threadName = Thread.currentThread().getName();
            System.out.format("%s: %s%n",
                              threadName,
                              message);
        }
        
}//end Player class